﻿using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using Microsoft.CSharp;
using System.Reflection;
using System.CodeDom.Compiler;
using System.Text;
using System.IO;
using System.Collections.Generic;

namespace EPiServer.Research.Gadgets.QuickWatch
{

    /// <summary>
    /// Performs a dynamic c# compilation on the fly
    /// </summary>
    public class DynamicCompiler
    {
        public Assembly CompiledAssembly;

        public CompilerResults Results;

        //BUG: if(true){return "hej";} else return "davs";

        public string Name { get; set; }

        public string[] Namespaces { get; set; }
        public const string TEMPNAMESPACE = "generatedcode";
        public const string TEMPCLASSNAME = "tempClass";
        public const string TEMPMETHODNAME = "Run";

        public object RunCode()
        {
            if (Results.NativeCompilerReturnValue==0)
            {
                try
                {
                    return Results.CompiledAssembly.GetType("generatedcode.tempClass").GetMethod("Run", BindingFlags.Static | BindingFlags.Public).Invoke(null, null);
                }
                catch (Exception exp)
                {
                    return exp.InnerException;
                }
            }
            else return null;
        }

        public string CodeWrapper(string Code, bool IndependentLines)
        {
            StringBuilder sb = new StringBuilder();
            //Setup namespaces
            foreach (string ns in Namespaces)
            {
                sb.AppendLine("using " + ns + ";");
            }
            sb.AppendLine("namespace " + TEMPNAMESPACE + "{");
            sb.AppendLine("public static class " + TEMPCLASSNAME + "{");
            //Trim code
            if (IndependentLines)
            {
                string[] codeparts = Code.Trim().Split(';').Where(s => s != string.Empty).Select(s => s.Trim()).ToArray();
                sb.AppendLine("public static object[] " + TEMPMETHODNAME + "(){");
                sb.AppendLine("return new object[]{");
                for (int i = 0; i < codeparts.Length; i++)
                {
                    sb.AppendLine(codeparts[i]);
                    if (i < (codeparts.Length - 1)) sb.AppendLine(",");
                }
                sb.AppendLine("};");
            }
            else
            {
                sb.AppendLine("public static object " + TEMPMETHODNAME + "(){");
                
                if ((Code.Trim().ToCharArray().Count(c => c == '\n') == 0) &&
                    ((!Code.Trim().EndsWith(";")) && (!Code.Trim().StartsWith("return "))))
                {
                    //Only 1 line of code with no return and semicolon in the end. Let's add it
                    sb.Append("return " + Code.Trim() + ";");
                }
                else
                {
                    sb.Append(Code);
                    sb.AppendLine("return \"You did not specify a return value\";");
                }
                //Set name
                int start = sb.ToString().IndexOf("return ");
                int stop = sb.ToString().IndexOf(";", start+7);
                Name = sb.ToString().Substring(start + 7, (stop - (start + 7)));

            }
            sb.AppendLine("}}}"); //End method, class, namespace
            return sb.ToString();
        }

        public void CompileCodeToMethod(string code, bool IndependentLines)
        {
            //Reference assemblies already loaded
            var assemblies = AppDomain.CurrentDomain
                .GetAssemblies().Where(a => !(a is System.Reflection.Emit.AssemblyBuilder))
                .Where(a => a.GlobalAssemblyCache  || (a.CodeBase ?? "").ToLower().Replace("/","\\").Contains(HttpContext.Current.Server.MapPath("~/bin/").ToLower())) 
                .Select(a => a.Location).ToArray();

            CompilerParameters parms = new CompilerParameters(assemblies);
            
            //Reference all the assemblies we have currently loaded
            parms.GenerateInMemory = true;
            parms.TreatWarningsAsErrors = false;
            parms.IncludeDebugInformation = false;
            parms.GenerateExecutable = false;
            
            parms.WarningLevel = 4;
            try
            {
                CSharpCodeProvider provider = new CSharpCodeProvider(new Dictionary<String, String> { { "CompilerVersion", "v3.5" } }); //Make provider flexible - vb, cs, etc.
                Results=provider.CompileAssemblyFromSource(parms,CodeWrapper(code,IndependentLines));
            }
            catch(Exception error)
            {
                //TODO: Output error
            }
            
        }

        public DynamicCompiler()
        {
        }
    }
}
